using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

using network;


namespace HpcClusterControllerLinux
{
	static class DomainName
	{
		public static string domain = Shell.run("dnsdomainname", "").Split('\n')[0];
	}
	
    public class Controller
    {
        private UDPclient clientUDP;
        private HashSet<String> reservedNodes;
        //private List<JobLinux> queue;
		private HpcNodeController nodeCtrl;

        public Controller(string nic)
        {			
            this.clientUDP = new UDPclient(nic);
            this.reservedNodes = new HashSet<string>();
            //this.queue = new List<JobLinux>();
			this.nodeCtrl = new HpcNodeController();
        }

        private void updateReservedSet(int jobId)
        {
            JobLinux job = new JobLinux(jobId);
            List<string> guids = nodeCtrl.getMoboGUIDs(job.allocatedNodes);
            reservedNodes.ExceptWith(guids);
        }

        void sendRequest(String msg)
        {
            int times = 5000;
            bool first = true;

            Debug.WriteLine(String.Format("Message to Leader: '{0}'", msg));

            while (true)
            {
                try
                {
                    clientUDP.sendMsg(msg);
                }
                catch (AckTimeout)
                {
                    Console.WriteLine("Timeout!");
                    if (first) // the first time, we call Socorro
                    {
                        Socorro.call();
                        first = false;
                    }
                    Thread.Sleep(times);
                    times = times * 2;
                    continue;
                }

                // if we're here, all went ok.
                break;
            }
        }

        //public void watcher_EventRecordWritten(object sender, FileSystemEventArgs e)
        public void processJobEvent(string logLine)
        {
			try {
				string[] fields = logLine.Split(';');
	            string status = fields[1];
	            string jobId = fields[2].Split('.')[0];
				
				Debug.WriteLine("Job status: " + status);
	            switch (status)
	            {
	                case "Q": // job queued
	                    jobCreated(jobId);
	                    break;                
	                case "E": // job ended
	                    // need cases for a cancelled and failed job
	                    jobEnded();
	                    break;
	                case "S": // job submitted, so, we can unlock the nodes reserved to it.
	                    updateReservedSet(Int32.Parse(jobId));
	                    break;
	            }
			} catch (Exception ex) {
				Debug.WriteLine(ex.Message);
				Debug.WriteLine(ex.StackTrace);
			}
        }

        private void jobCreated(string jobID)
        {
            JobLinux job = new JobLinux(jobID);
			Debug.WriteLine(String.Format("Job: {0}, {1} nodes, {2}", job.jobid, job.nds, job.state));
			
            if (job.state == JobState.Queued)
            {
                int minNodes = job.nds;

                List<string> offNodes = nodeCtrl.getOffLineNodes();
                List<string> idleNodes = nodeCtrl.getIdleNodes();

                if (idleNodes.Count + offNodes.Count - reservedNodes.Count >= minNodes)
                {
                    foreach (var m in idleNodes)
                    {
                        reservedNodes.Add(m.ToString()); // m = machineguid
                        minNodes -= 1; // update job demand

                        if (minNodes == 0)
                        {
                            break; // we don't need more
                        }

                    }
                    // now, if we still need machines, we use the offline set.
                    // the minNode is updated
                    if (minNodes > 0)
                    {
						Debug.WriteLine("No enough nodes. Requesting more to the Leader");
                        requestWake(offNodes, minNodes);
                    }
                }
                else
                {
                    // we can't allocate resources for this job, so we keep it in the queue to look later
					Debug.WriteLine("No more nodes to request. Job queued");
                    //queue.Add(job);
                }
            }
        }

        private void jobEnded()
        {
			// we need to wait for any action of the scheduler
			// to get consistent results in the queue
            Thread.Sleep(7000);

            List<string> offNodes = nodeCtrl.getOffLineNodes();
            List<string> idleNodes = nodeCtrl.getIdleNodes();
			
			int minNodes = JobLinux.GetSmallestJob();
			Debug.WriteLine("min: " + minNodes);
			
            if (minNodes > idleNodes.Count + offNodes.Count)
            {
				// the smallest job in the queue is large than the available resources
                // so, no job can run, we'll shutdown the idle nodes
				Debug.WriteLine("No job to run");
                requestDown();
                return;
            }

			// we will use all the idle nodes plus some of offline nodes
            requestWake(offNodes, minNodes - idleNodes.Count);
        }

        void requestWake(List<string> nodes, int n)
        {
            // join the n first non reserved results
            String msg = String.Format("{0}", (int)MsgType.START_VM);
            int used = 0;

            foreach (var item in nodes)
            {
                string nodeGuid = item.ToString(); // MachineGuid
                if (reservedNodes.Add(nodeGuid))
                {
                    // we reserve the nodes we choose.
                    msg = msg + " " + nodeGuid;
                    if (++used == n) break;
                }
            }

            this.sendRequest(msg);
        }

        void requestDown()
        {
			Debug.WriteLine("Request down");
            List<string> idleNodes = nodeCtrl.getIdleNodes();

            if (idleNodes.Count == 0)
            {
                Debug.WriteLine("No node to suspend");
                return;
            }

            String msg = String.Format("{0}", (int)MsgType.SAVE_VM);
            foreach (var nodeGuid in idleNodes)
            {
                msg = msg + " " + nodeGuid;
            }

            this.sendRequest(msg);
        }
	}
}
